package cn.iocoder.boot;

import java.util.*;
import java.util.stream.Collectors;

public class ItemCFRecommender {

    // 用户-物品评分矩阵
    private Map<Integer, Map<Integer, Double>> userItemMatrix;

    public ItemCFRecommender(Map<Integer, Map<Integer, Double>> userItemMatrix) {
        this.userItemMatrix = userItemMatrix;
    }

    // 计算两个物品的余弦相似度
    public static double cosineSimilarity(Map<Integer, Double> item1Ratings, Map<Integer, Double> item2Ratings) {
        double dotProduct = 0.0;
        double norm1 = 0.0;
        double norm2 = 0.0;

        Set<Integer> commonUsers = new HashSet<>(item1Ratings.keySet());
        commonUsers.retainAll(item2Ratings.keySet());

        for (int user : commonUsers) {
            double r1 = item1Ratings.get(user);
            double r2 = item2Ratings.get(user);
            dotProduct += r1 * r2;
            norm1 += Math.pow(r1, 2);
            norm2 += Math.pow(r2, 2);
        }

        return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2));
    }

    // 构建物品相似度矩阵
    public Map<Integer, Map<Integer, Double>> buildItemSimilarityMatrix() {
        Map<Integer, Map<Integer, Double>> itemSimilarity = new HashMap<>();

        // 收集所有物品
        Set<Integer> allItems = new HashSet<>();
        for (Map<Integer, Double> itemRatings : userItemMatrix.values()) {
            allItems.addAll(itemRatings.keySet());
        }

        // 计算每对物品的相似度
        for (int item1 : allItems) {
            Map<Integer, Double> item1Ratings = new HashMap<>();
            for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMatrix.entrySet()) {
                int user = entry.getKey();
                double rating = entry.getValue().getOrDefault(item1, 0.0);
                item1Ratings.put(user, rating);
            }

            itemSimilarity.put(item1, new HashMap<>());
            for (int item2 : allItems) {
                if (item1 != item2) {
                    Map<Integer, Double> item2Ratings = new HashMap<>();
                    for (Map.Entry<Integer, Map<Integer, Double>> entry : userItemMatrix.entrySet()) {
                        int user = entry.getKey();
                        double rating = entry.getValue().getOrDefault(item2, 0.0);
                        item2Ratings.put(user, rating);
                    }

                    double similarity = cosineSimilarity(item1Ratings, item2Ratings);
                    itemSimilarity.get(item1).put(item2, similarity);
                }
            }
        }

        return itemSimilarity;
    }

    // 为目标用户生成推荐物品
    public Set<Integer> generateRecommendations(int targetUser, int k) {
        Map<Integer, Map<Integer, Double>> itemSimilarity = buildItemSimilarityMatrix();
        Map<Integer, Double> userRatings = userItemMatrix.get(targetUser);
        Set<Integer> recommendedItems = new HashSet<>();

        // 找出目标用户评分最高的前 k 个物品
        List<Map.Entry<Integer, Double>> ratedItems = new ArrayList<>(userRatings.entrySet());
        ratedItems.sort((e1, e2) -> e2.getValue().compareTo(e1.getValue()));
        List<Integer> topKItems = ratedItems.stream().limit(k).map(Map.Entry::getKey).collect(Collectors.toList());

        // 对于每个高频物品，推荐与其相似度最高的物品
        for (int item : topKItems) {
            Map<Integer, Double> similarItems = itemSimilarity.getOrDefault(item, new HashMap<>());
            if (similarItems.isEmpty()) continue;

            // 找出与当前物品相似度最高的未被用户评分的物品
            double maxSimilarity = -1;
            int recommendedItem = -1;
            for (Map.Entry<Integer, Double> similarityEntry : similarItems.entrySet()) {
                int similarItem = similarityEntry.getKey();
                double similarity = similarityEntry.getValue();

                if (!userRatings.containsKey(similarItem) && similarity > maxSimilarity) {
                    maxSimilarity = similarity;
                    recommendedItem = similarItem;
                }
            }

            if (recommendedItem != -1) {
                recommendedItems.add(recommendedItem);
            }
        }

        return recommendedItems;
    }

    // 输出推荐理由
    public void explainRecommendations(int targetUser, Set<Integer> recommendedItems) {
        System.out.println("推荐理由");
        System.out.println("------------------------------------");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendedItems);

        for (int item : recommendedItems) {
            System.out.println("推荐物品 " + item + " 的原因：");
            System.out.println("该物品与目标用户评分较高的物品具有较高的相似度。");
            System.out.println("相似度计算基于余弦相似度算法，通过比较物品在用户评分向量上的夹角来确定相似性。");
            System.out.println("余弦相似度值越接近1，表示两个物品越相似。");
        }

        System.out.println("------------------------------------");
    }

    public static void main(String[] args) {
        // 示例用户-物品评分矩阵
        Map<Integer, Map<Integer, Double>> userItemMatrix = new HashMap<>();

        // 手动创建用户 1 到用户 12 的评分
        for (int i = 1; i <= 12; i++) {
            Map<Integer, Double> userRatings = new HashMap<>();
            for (int j = 101; j <= 110; j++) {
                if (j % 10 <= 5) {
                    userRatings.put(j, Math.random() * 5);
                }
            }
            userItemMatrix.put(i, userRatings);
        }

        ItemCFRecommender recommender = new ItemCFRecommender(userItemMatrix);

        // 为目标用户生成推荐物品
        int targetUser = 1;
        int k = 2;
        Set<Integer> recommendations = recommender.generateRecommendations(targetUser, k);

        // 输出推荐结果
        System.out.println("====================================");
        System.out.println("推荐结果");
        System.out.println("====================================");
        System.out.println("目标用户: " + targetUser);
        System.out.println("推荐物品: " + recommendations);
        System.out.println("------------------------------------");

        // 输出推荐理由
        recommender.explainRecommendations(targetUser, recommendations);
    }
}